package com.sky.service.impl;

import com.github.pagehelper.Page;
import com.github.pagehelper.PageHelper;
import com.sky.constant.MessageConstant;
import com.sky.constant.PasswordConstant;
import com.sky.constant.StatusConstant;
import com.sky.context.BaseContext;
import com.sky.dto.EmployeeDTO;
import com.sky.dto.EmployeeLoginDTO;
import com.sky.dto.EmployeePageQueryDTO;
import com.sky.dto.PasswordEditDTO;
import com.sky.entity.Employee;
import com.sky.exception.AccountLockedException;
import com.sky.exception.AccountNotFoundException;
import com.sky.exception.PasswordErrorException;
import com.sky.mapper.EmployeeMapper;
import com.sky.result.PageResult;
import com.sky.service.EmployeeService;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.DigestUtils;

import java.time.LocalDateTime;
import java.util.List;

@Service
public class EmployeeServiceImpl implements EmployeeService {

    @Autowired
    private EmployeeMapper employeeMapper;

    /**
     * 员工登录
     *
     * @param employeeLoginDTO
     * @return
     */
    //接口类派送的登录方法功能
    public Employee login(EmployeeLoginDTO employeeLoginDTO) {
        //通过传入的DTO对象获得用户名和密码
        String username = employeeLoginDTO.getUsername();
        String password = employeeLoginDTO.getPassword();

        //1、根据用户名查询数据库中的数据
        Employee employee = employeeMapper.getByUsername(username);

        //2、处理各种异常情况（用户名不存在、密码不对、账号被锁定）
        if (employee == null) {
            //账号不存在
            throw new AccountNotFoundException(MessageConstant.ACCOUNT_NOT_FOUND);
        }
        //使用md5进行加密 并且比对
        password = DigestUtils.md5DigestAsHex(password.getBytes());
        //密码比对
        if (!password.equals(employee.getPassword())) {
            //密码错误
            throw new PasswordErrorException(MessageConstant.PASSWORD_ERROR);
        }

        if (employee.getStatus() == StatusConstant.DISABLE) {
            //账号被锁定
            throw new AccountLockedException(MessageConstant.ACCOUNT_LOCKED);
        }

        //3、返回实体对象
        return employee;
    }

    //接口类派送的添加员工的功能
    @Override
    public void addEmployyee(EmployeeDTO employeeDTO) {
        Employee employee = Employee.builder()
                //通过链式编程创建对象
                //.createTime(LocalDateTime.now())
                //.updateTime(LocalDateTime.now())
                //.createUser(BaseContext.getCurrentId())
                //.updateUser(BaseContext.getCurrentId())
                //密码没有默认值 自己传入一个加密厚的密码 默认123456 直接加密
                .password(DigestUtils.md5DigestAsHex(PasswordConstant.DEFAULT_PASSWORD.getBytes()))
                .build();
        //将其他的数据通过DTO进行映射(属性名必须一致)
        //要记得这些操作使用过的 digestUtils是加密的
        //BeanUtils 是映射的
        //还有实现了一个extendMessageConverters的方法 是一个获得消息转换器的方法
        //通过创建一个自定义的消息转换器使用里面的setobjectmapping方法 自定义的消息对象传入
        //然后通过消息转换器(是一个集合)传入自定义消息转换器 因为是add方法 它会加到尾部
        //这时候是不会使用到我们的自定义消息转换器的 需要通过自定义索引来将它放到前面
        //add方法它的重载方法是有一个可以放入索引的 所以我们需要通过这个重载方法指定
        //自定义消息转换器的位置 使得可以执行自定义消息转换器的内容
        //自定义消息转换器的内容 大概就是将转换为JSON格式的时间类型的对象进行转换
        //将序列化时会抛出异常的状态调成false 然后通过里面的一个方法将会抛出异常这个特性移除
        //然后就进行序列化传入时间类字节码 以及利用序列化我们定义的格式 后在进行反序列化
        //最后通过注册功能模块的方法 将转换成功的时间对象进行注册.实现json格式的类型转换
        BeanUtils.copyProperties(employeeDTO, employee);

        //最后调用mapper层的addEmployyee的方法 传入拷贝DTO的employee对象
        employeeMapper.addEmployyee(employee);

    }

    @Override
    //接口类派送的分类查询的功能
    public PageResult pageSelect(EmployeePageQueryDTO employeePageQueryDTO) {

        //通过传入的DTO对象 使用分页插件将页码和页数传入 底层会帮忙进行分页并且计算总页数
        PageHelper.startPage(employeePageQueryDTO.getPage(), employeePageQueryDTO.getPageSize());

        //通过mapper层的pageSelect的方法 传入DTO对象的名字 因为只有根据名字模糊查询
        // 可传可不传 不传默认匹配所有 获得回来的是一个员工Employee的集合 因为名字不是唯一的
        // 可能不止一个 使用集合接收
        List<Employee> employees = employeeMapper.pageSelect(employeePageQueryDTO.getName());

        //通过插件的Page类(底层也是一个list集合) 将list<Employee>的集合进行强转
        Page p = (Page) employees;

        //通过里面的getTotal方法以及getResult方法获得分页好的员工集合以及总页数
        //并且封装到需要返回的PageResult这个实体类对象中 因为需要返回的是两个数据
        //所以需要通过实体类封装
        return new PageResult(p.getTotal(), p.getResult());
    }

    //接口类派送的员工账户启动/暂停的功能
    @Override
    public void statusStartOrStop(Long id, Integer status) {
        //通过传入的员工id 以及启动/停止的功能值
    //个人感受:
        //构造一个员工对象传入 这样可以增加后续代码的复用性
        //后面因为不想通过getname获得其他表的时候使用了多表通过VO封装
        //但是就是因为封装的是VO 后续需要接连调用别的时候需要重复的创建对象并且进行copy
        //所以以后最好返回的使用一个实体类对象 然后多做一步进行封装
        Employee employee = Employee.builder()
                .id(id)
                .status(status)
                //.updateTime(LocalDateTime.now())
                //.updateUser(BaseContext.getCurrentId())
                .build();


        //这里也是用的builder()构造对象的方式构造的
        //传入对象 这里的方法是update 因为也算是一种修改 所以里面的代码写的通用一些
        //这样到时候修改员工就可以一起调用该方法
        employeeMapper.updateEmployee(employee);
    }

    //接口类派送的修改员工的功能
    @Override
    public void updateEmployyee(EmployeeDTO employeeDTO) {

        //构造一个实体类对象 将DTO对象进行拷贝
        Employee employee = Employee.builder()
                //.updateTime(LocalDateTime.now())
                //.updateUser(BaseContext.getCurrentId())
                .build();
        BeanUtils.copyProperties(employeeDTO, employee);

        //也是一样 通过update的方法传入员工对象进行修改
        employeeMapper.updateEmployee(employee);
    }

    //接口类派送的根据id查询员工的功能
    @Override
    public Employee selectById(Long id) {

        //这里直接通过id进行查询直接返回
        return employeeMapper.selectById(id);
    }

    //接口类派送的修改员工的功能
    @Override
    public void updatePassword(PasswordEditDTO passwordEditDTO) {
        //通过传入的DTO对象 把用户名id和密码封装到实体类对象中

        Employee employee = Employee.builder()
                //因为判断密码需要进行加密 这里使用md5进行加密比对 否则会报错 因为数据库存的也是md5加密后的
                .password(DigestUtils.md5DigestAsHex(passwordEditDTO.getOldPassword().getBytes()))
                //用户id
                .id(BaseContext.getCurrentId())
                .build();

        //通过mapper的checkEmployee来校验用户 通过主键id 查询用户的密码是否是当前密码
        //因为这里不是登录校验的密码 是登录后的修改密码所以不需要用户名
        Employee checkEmployee = employeeMapper.checkEmployee(employee);

        //校验如果旧密码和新密码一致 则抛异常
        if (checkEmployee.getPassword().equals(DigestUtils.md5DigestAsHex(passwordEditDTO.getOldPassword().getBytes()))) {
            throw new PasswordErrorException(MessageConstant.OLDPASSWORD_OR_NEWPASSWORD_SAME);
        }
        //如果旧密码不存在也抛出初始密码错误异常
        if (checkEmployee == null) {
            throw new PasswordErrorException(MessageConstant.PASSWORD_EDIT_FAILED);
        }

        //如果前面的校验都没问题了在通过setPassword的方法将新的密码传入该对象
        //如果判断到这里说明是有该对象的 直接进行密码修改
        checkEmployee.setPassword(DigestUtils.md5DigestAsHex(passwordEditDTO.getNewPassword().getBytes()));

        //直接通过update的方法将修改密码后的用户传入
        employeeMapper.updateEmployee(checkEmployee);
    }


}
